#include "DEHT.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

/*		PROTOTYPES		*/

DEHT_DISK_PTR bucketPos(DEHT *ht, int b);

DEHT_DISK_PTR lastBlock(DEHT *ht, DEHT_DISK_PTR block);

int readBlock(const DEHT *ht,const DEHT_DISK_PTR blockPtr, void *block);

int checkBlock(DEHT *ht, void *block, const unsigned char *key);

DEHT_DISK_PTR getNextBlock(DEHT *ht,void *block);

DEHT_DISK_PTR getDataPtr(DEHT *ht,void *block, int pair);

int getData(DEHT *ht, DEHT_DISK_PTR dataPtr, const unsigned char *data, int dataMaxAllowedLength);

int getBlockSize(DEHT *ht,void *block);

void printTable(DEHT *ht);   /*DEBUG*/

/*		###########		*/

DEHT *create_empty_DEHT(const char *prefix,/*add .key and .data to open two files return NULL if fail creation*/
		hashKeyIntoTableFunctionPtr hashfun, hashKeyforEfficientComparisonFunctionPtr validfun,
		const char *dictName,   /*e.g. MD5\0 */
		int numEntriesInHashTable, int nPairsPerBlock, int nBytesPerKey, int nUnrelated){ /*optimization preferences*/

	FILE *keyFP = NULL, *dataFP = NULL;
	char keyFilename[80] = {0}, dataFilename[80] = {0};
	char *table = NULL;
	DEHT *deht = NULL;
	struct DEHTpreferences *header = NULL;
	void * unrelated = NULL;

	/*arguments check*/
	if ((prefix == NULL) || (hashfun == NULL) || (validfun == NULL) || (dictName == NULL) ||
			(numEntriesInHashTable < 1) || (nPairsPerBlock < 1) || (nBytesPerKey < 1))
		return NULL;

	/*create strings for file names*/
	strcpy(keyFilename, prefix);
	strcat(keyFilename, ".key");
	strcpy(dataFilename, prefix);
	strcat(dataFilename, ".data");


	keyFP = fopen(keyFilename, "rb");
	if (keyFP != NULL){
		fclose(keyFP);
		fprintf(stderr, "%s.key already exists\n", prefix);
		return NULL;
	}

	dataFP = fopen(dataFilename, "rb");
	if (dataFP != NULL){
		fclose(dataFP);
		fprintf(stderr, "%s.data already exists\n", prefix);
		return NULL;
	}

	/*create the files for writing*/
	keyFP = fopen(keyFilename, "wb+");
	if (keyFP == NULL){
		fprintf(stderr, "problem creating %s\n", keyFilename);
		return NULL;
	}

	dataFP = fopen(dataFilename, "wb+");
	if (dataFP == NULL){
		remove(keyFilename);
		fprintf(stderr, "problem creating %s\n", dataFilename);
		fclose(keyFP);
		return NULL;
	}

	deht = (DEHT *)malloc(sizeof(DEHT));
	if (deht == NULL){
		fprintf(stderr, "mem. allocation failure\n");
		fclose(keyFP);
		fclose(dataFP);
		return NULL;
	}

	/*Assign DEHT attributes*/
	strcpy(deht->sKeyfileName, keyFilename);
	strcpy(deht->sDatafileName, dataFilename);
	deht->keyFP = keyFP;
	deht->dataFP = dataFP;
	deht->hashFunc = hashfun;
	deht->comparisonHashFunc = validfun;
	deht->hashTableOfPointersImageInMemory = NULL;
	deht->hashPointersForLastBlockImageInMemory = NULL;
	deht->anLastBlockSize = NULL;
	/*assign header attributes*/
	strcpy(deht->header.sDictionaryName,dictName);
	deht->header.numEntriesInHashTable = numEntriesInHashTable;
	deht->header.nPairsPerBlock = nPairsPerBlock;
	deht->header.nBytesPerValidationKey = nBytesPerKey;
	deht->header.numUnrelatedBytesSaved = nUnrelated;

	/*write the header to prefix.key*/
	header = &(deht->header);
	if (fwrite(header, sizeof(struct DEHTpreferences), 1, keyFP) != 1){
		perror(keyFilename);
		fclose(keyFP);
		fclose(dataFP);
		free(deht);
		return NULL;
	}

	/*write a space-saver for the unrelated bytes*/
	unrelated = calloc(nUnrelated, 1);
	if (unrelated == NULL){
		fprintf(stderr, "mem. allocation failure\n");
		fclose(keyFP);
		fclose(dataFP);
		free(deht);
		return NULL;
	}
	if (fwrite(unrelated, 1, nUnrelated, keyFP) != nUnrelated){
		fprintf(stderr, "mem. allocation failure\n");
		fclose(keyFP);
		fclose(dataFP);
		free(deht);
		free(unrelated);
		return NULL;
	}
	free(unrelated);

	table = (char *)calloc(numEntriesInHashTable, sizeof(DEHT_DISK_PTR));
	if (table == NULL){
		fprintf(stderr, "mem. allocation failure\n");
		fclose(keyFP);
		fclose(dataFP);
		free(deht);
		free(table);
		return NULL;
	}
	if (fwrite(table,sizeof(DEHT_DISK_PTR), numEntriesInHashTable, keyFP) != numEntriesInHashTable){
		perror(keyFilename);
		fclose(keyFP);
		fclose(dataFP);
		free(deht);
		free(table);
		return NULL;
	}
	free(table);
	/*flush the buffer*/
	fseek(keyFP, 0, 1);

	return deht;
}

DEHT *load_DEHT_from_files(const char *prefix,
		hashKeyIntoTableFunctionPtr hashfun, hashKeyforEfficientComparisonFunctionPtr validfun){

	char keyFilename[80] = {0}, dataFilename[80] = {0};
	FILE *keyFP = NULL, *dataFP = NULL;
	DEHT *deht = NULL;
	struct DEHTpreferences *header = NULL;
	int c1 = 0;

	/*arguments check*/
	if ((prefix == NULL) || (hashfun == NULL) || (validfun == NULL))
		return NULL;

	/*create strings for file names*/
	strcpy(keyFilename, prefix);
	strcat(keyFilename, ".key");
	strcpy(dataFilename, prefix);
	strcat(dataFilename, ".data");

	/*open the files if they exist*/
	if (!(keyFP = fopen(keyFilename, "rb+"))){
		fprintf(stderr, "%s doesn't exists\n", keyFilename);
		return NULL;
	}
	if (!(dataFP = fopen(dataFilename, "rb+"))){
		fprintf(stderr, "%s doesn't exists\n", dataFilename);
		fclose(keyFP);
		return NULL;
	}
	/*allocate mem. for the deht we want to return*/
	deht = (DEHT *)calloc(1, sizeof(DEHT));
	if (deht == NULL){
		fprintf(stderr, "unable to allocate mem. for DEHT\n");
		fclose(keyFP);
		fclose(dataFP);
		return NULL;
	}

	/*assign attributes to deht*/
	strcpy(deht->sKeyfileName, keyFilename);
	strcpy(deht->sDatafileName, dataFilename);
	deht->keyFP = keyFP;
	deht->dataFP = dataFP;
	deht->hashFunc = hashfun;
	deht->comparisonHashFunc = validfun;
	deht->hashPointersForLastBlockImageInMemory = NULL;
	deht->hashTableOfPointersImageInMemory = NULL;
	deht->anLastBlockSize = NULL;
	header = &(deht->header);
	/*read from prefix.key the header*/
	c1 = fread(header, sizeof(struct DEHTpreferences), 1, keyFP);
	if (c1 != 1) {
		perror(keyFilename);
		fclose(keyFP);
		fclose(dataFP);
		free(deht);
		return NULL;
	}

	fseek(keyFP, 0,1);

	return deht;
}

/*NOTE: along insert_uniquely and add we kept the convention of foundFlag as a boolean indicating that we are adding a pair to an empty bucket (first block 		of the bucket) and newBlockFlag which indicates that we need to write a space saver after the last pair we wrote (we write a new block so we write 		a space saver right after the pair for the rest of the block)

	also methodicaly they both work first on checkign where we need to write the new info to .key and .data (only fread operations)
	and then writing the info to the right places (only fwrite operations) with a buffer flush in between

*/

int insert_uniquely_DEHT ( DEHT *ht, const unsigned char *key, int keyLength,
		const unsigned char *data, int dataLength){

	hashKeyIntoTableFunctionPtr hash1 = NULL;
	hashKeyforEfficientComparisonFunctionPtr hash2 = NULL;
	DEHT_DISK_PTR bucket = 0, bucketPtr = 0, blockPtr = 0, prevPtr = 0, dataPtr = 0, nextPtr = 0, newPairPtr = 0;
	unsigned char *valKey = NULL;
	char dataBuffer[80] = {0};
	FILE *keyFP = NULL, *dataFP = NULL;
	void *block = NULL, *filler = NULL;
	int i = 0, fillerSize = 0, foundFlag = 0, newBlockFlag = 0;
	unsigned char c = '\0';

	if ((ht == NULL) || (key == NULL) || (keyLength < 1) || (data == NULL) || (dataLength < 0))
		return DEHT_STATUS_FAIL;

	/*flush just in case*/
	fseek(ht->keyFP, 0, 1);
	fseek(ht->dataFP, 0, 1);

	valKey = (unsigned char *)calloc(ht->header.nBytesPerValidationKey, sizeof(unsigned char));
	if (valKey == NULL) return DEHT_STATUS_FAIL;

	hash1 = ht->hashFunc;
	hash2 = ht->comparisonHashFunc;
	keyFP = ht->keyFP;
	dataFP = ht->dataFP;

	bucket = hash1(key, keyLength, ht->header.numEntriesInHashTable);
	bucketPtr = bucketPos(ht, bucket);
	hash2(key, keyLength, valKey);

	/*if the bucket is empty we can use add_DEHT to finish-up for us*/
	if (ht->hashTableOfPointersImageInMemory != NULL){
		if (ht->hashTableOfPointersImageInMemory[bucket] == 0){
			free(valKey);
			return add_DEHT(ht, key, keyLength, data, dataLength);
		}
		else
			blockPtr = ht->hashTableOfPointersImageInMemory[bucket];
	}
	else {
		fseek(keyFP, bucketPtr, 0);
		if (fread(&blockPtr, sizeof(DEHT_DISK_PTR), 1, keyFP) != 1){
			free(valKey);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}
		if (blockPtr == 0){
			free(valKey);
			return add_DEHT(ht, key, keyLength, data, dataLength);
		}
	}

	/*allocate a block size buffer for reading info along the blocks-list*/
	block = calloc(1, ht->header.nPairsPerBlock*(sizeof(DEHT_DISK_PTR)+ht->header.nBytesPerValidationKey) + sizeof(DEHT_DISK_PTR));
	if (block == NULL) {
		free(valKey);
		return DEHT_STATUS_FAIL;
	}

	/*search every block until we finished the list to make sure we don't have the same key signature already - need to overwrite*/
	prevPtr = blockPtr;
	while (blockPtr != 0){
		if (readBlock(ht, blockPtr, block) == 0){
			free(valKey);
			free(block);
			return DEHT_STATUS_FAIL;
		}
		i = checkBlock(ht, block, valKey);
		if (i != -1){
			foundFlag = 1;
			break;
		}
		prevPtr = blockPtr;
		blockPtr = getNextBlock(ht, block);
	}

	/*if we don't have to replace a data value -- foundFlag is 1 iff we found the key sig. in the above while loop
		go to the last block and find where we need to write the new pair
	*/
	if (!foundFlag){
		i = getBlockSize(ht, block);
		if (i < ht->header.nPairsPerBlock){
			newPairPtr = prevPtr + i*(ht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR));
		}
		else {
			newBlockFlag = 1;
			fseek(keyFP, 0, 2);
			newPairPtr = ftell(keyFP);
			nextPtr = prevPtr + ht->header.nPairsPerBlock*(ht->header.nBytesPerValidationKey+sizeof(DEHT_DISK_PTR));
		}

		fseek(dataFP, 0, 2);
		dataPtr = ftell(dataFP);
	}
	/*we found the key sig. and we need to replace the data in the .data file
		we read the data and find out if we can write to the current space we have for it
		if we can we overwrite (use less than/equal the space we had until the switch)
		if we can not overwrite add the new data info to the end of the .data file 
	*/
	else {
		if (getData(ht, getDataPtr(ht, block, i), (unsigned char*)dataBuffer, 80) == 0){
			free(block);
			free(valKey);
			return DEHT_STATUS_FAIL;
		}
		if (dataLength <= strlen(dataBuffer)){
			dataPtr = getDataPtr(ht, block, i);
		}
		else {
			fseek(dataFP, 0,2);
			dataPtr = ftell(dataFP);
		}
	}

	/*flush buffers before writing*/
	fseek(keyFP, 0, 1);
	fseek(dataFP, 0, 1);

	if (!foundFlag){
		fseek(keyFP, newPairPtr, 0);
		if (fwrite(valKey, sizeof(unsigned char), ht->header.nBytesPerValidationKey, keyFP) != ht->header.nBytesPerValidationKey){
			free(block);
			free(valKey);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}
		if (fwrite(&dataPtr, sizeof(DEHT_DISK_PTR), 1, keyFP) != 1){
			free(block);
			free(valKey);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}

		/*write the space saver for the rest of the block in the .key file*/
		if (newBlockFlag){
			fillerSize = (ht->header.nPairsPerBlock - 1)*(sizeof(DEHT_DISK_PTR)+ht->header.nBytesPerValidationKey) + sizeof(DEHT_DISK_PTR);
			filler = calloc(fillerSize,1);
			if (filler == NULL){
				free(block);
				free(valKey);
				return DEHT_STATUS_FAIL;
			}
			if (fwrite(filler, fillerSize, 1, keyFP) != 1){
				free(block);
				free(valKey);
				free(filler);
				perror(ht->sKeyfileName);
				return DEHT_STATUS_FAIL;
			}
			free(filler);

			/*this is a new block so we need to write where it is at in the next of the prev. block (or the bucket if this is the first block)*/
			fseek(keyFP, nextPtr, 0);
			if (fwrite(&newPairPtr, sizeof(DEHT_DISK_PTR), 1, keyFP) != 1){
				free(block);
				free(filler);
				perror(ht->sKeyfileName);
				return DEHT_STATUS_FAIL;
			}

		}
	}
	else {
		fseek(keyFP, blockPtr + i*(sizeof(DEHT_DISK_PTR) + ht->header.nBytesPerValidationKey) + ht->header.nBytesPerValidationKey, 0);
		if (fwrite(&dataPtr, sizeof(DEHT_DISK_PTR), 1, keyFP) != 1){
			free(block);
			free(valKey);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}
	}

	/*write new data to dataFP*/
	fseek(dataFP, dataPtr, 0);
	if (fwrite((signed char *)data, sizeof(unsigned char), dataLength, ht->dataFP) != dataLength){
		free(valKey);
		free(block);
		perror(ht->sDatafileName);
		return DEHT_STATUS_FAIL;
	}
	if (fwrite(&c, sizeof(unsigned char), 1, ht->dataFP) != 1) {
		free(valKey);
		free(block);
		perror(ht->sDatafileName);
		return DEHT_STATUS_FAIL;
	}

	if (newBlockFlag){
		if (ht->hashPointersForLastBlockImageInMemory != NULL)
			ht->hashPointersForLastBlockImageInMemory[bucket] = newPairPtr;
	}
	if (ht->anLastBlockSize != NULL){
		ht->anLastBlockSize[bucket]++;
		if (ht->anLastBlockSize[bucket] == ht->header.nPairsPerBlock+1)
			ht->anLastBlockSize[bucket] = 1;
	}

	free(block);
	free(valKey);
	if (foundFlag)
		return DEHT_STATUS_NOT_NEEDED;
	else
		return DEHT_STATUS_SUCCESS;
}

int add_DEHT ( DEHT *ht, const unsigned char *key, int keyLength,
		const unsigned char *data, int dataLength){
	FILE *keyFP = NULL, *dataFP = NULL;
	hashKeyIntoTableFunctionPtr hash1 = NULL;
	hashKeyforEfficientComparisonFunctionPtr hash2 = NULL;
	int bucket = 0, fillerSize = 0;
	unsigned char *valKey = NULL;
	DEHT_DISK_PTR blockPtr = 0, bucketPtr = 0;
	DEHT_DISK_PTR newPairPtr = 0, nextPtr = 0, dataPtr = 0;
	int i = 0, newBlockFlag = 0, firstFlag = 0;
	void *block = NULL, *filler;
	unsigned char c = '\0';

	/*arguments check*/
	if ((ht == NULL) || (key == NULL) || (keyLength < 1) || (data == NULL) || (dataLength < 0))
		return DEHT_STATUS_FAIL;

	/*flush just in case*/
	fseek(ht->keyFP, 0, 1);
	fseek(ht->dataFP, 0, 1);

	valKey = (unsigned char *)calloc(ht->header.nBytesPerValidationKey, sizeof(unsigned char));
	if (valKey == NULL) return DEHT_STATUS_FAIL;

	keyFP = ht->keyFP;
	dataFP = ht->dataFP;

	hash1 = ht->hashFunc;
	hash2 = ht->comparisonHashFunc;

	bucket = hash1(key, keyLength, ht->header.numEntriesInHashTable);
	bucketPtr = bucketPos(ht, bucket);
	hash2(key, keyLength, valKey);

	/*set blockPtr to the last block and read it from file*/
	if (ht->hashPointersForLastBlockImageInMemory != NULL)
		blockPtr = ht->hashPointersForLastBlockImageInMemory[bucket];   /*might be 0 */
	else if (ht->hashTableOfPointersImageInMemory != NULL) {
		blockPtr = ht->hashTableOfPointersImageInMemory[bucket];   		/*might be 0*/
		blockPtr = lastBlock(ht, blockPtr);
		if (blockPtr == -1) {
			free(valKey);
			return DEHT_STATUS_FAIL;
		}
	}
	else {
		/*read the bucket*/
		fseek(ht->keyFP, bucketPtr, 0);
		if (fread(&blockPtr, sizeof(DEHT_DISK_PTR), 1, keyFP) != 1){
			free(valKey);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}
	}

	/*this means that we are adding to an empty bucket in the table*/
	if (blockPtr == 0) {
		firstFlag = 1;
		newBlockFlag = 1;
		nextPtr = bucketPtr;
		fseek(ht->keyFP, 0, 2);
		newPairPtr = ftell(ht->keyFP);
	}
	/*otherwise we go to the location of the last block of the list*/
	else if (ht->hashPointersForLastBlockImageInMemory == NULL){
		blockPtr = lastBlock(ht, blockPtr);
		if (blockPtr == -1) {
			free(valKey);
			return DEHT_STATUS_FAIL;
		}
	}
	if (!firstFlag){
		/*set i to be the right pair to write*/
		if (ht->anLastBlockSize != NULL)
			i = ht->anLastBlockSize[bucket];
		else {
			/*we need to read the block to see what size it is*/
			block = calloc(1, ht->header.nPairsPerBlock*(sizeof(DEHT_DISK_PTR)+ht->header.nBytesPerValidationKey) + sizeof(DEHT_DISK_PTR));
			if (block == NULL) {
				free(valKey);
				return DEHT_STATUS_FAIL;
			}
			if (readBlock(ht, blockPtr, block) == 0) {
				free(valKey);
				free(block);
				return DEHT_STATUS_FAIL;
			}
			i = getBlockSize(ht, block);
			free(block);
		}

		if (i < ht->header.nPairsPerBlock){
			newPairPtr = blockPtr + i*(ht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR));
		}
		else {
			nextPtr = blockPtr + ht->header.nPairsPerBlock*(ht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR));
			fseek(keyFP, 0, 2);
			newPairPtr = ftell(keyFP);
			newBlockFlag = 1;
		}
	}
	/*by now we have found out if: 1) the bucket is empty
	 * 			       2) the block is full and in this case where to write the pointer to the new block
	 * 			       3) if the block is not full where to add the new pair
	 */
	fseek(keyFP, newPairPtr, 0);  /*flush the buffer*/

	if (fwrite(valKey, sizeof(unsigned char), ht->header.nBytesPerValidationKey,ht->keyFP) != ht->header.nBytesPerValidationKey){
		free(valKey);
		perror(ht->sKeyfileName);
		return DEHT_STATUS_FAIL;
	}
	fseek(dataFP, 0, 2);
	dataPtr = ftell(dataFP);
	if (fwrite(&dataPtr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP) != 1){
		free(valKey);
		perror(ht->sKeyfileName);
		return DEHT_STATUS_FAIL;
	}
	if (newBlockFlag){
		/*write the rest of the new block*/
		fillerSize = (ht->header.nPairsPerBlock - 1)*(sizeof(DEHT_DISK_PTR)+ht->header.nBytesPerValidationKey) + sizeof(DEHT_DISK_PTR);
		filler = calloc(fillerSize,1);
		if (filler == NULL) {
			free(valKey);
			return DEHT_STATUS_FAIL;
		}
		if (fwrite(filler, fillerSize, 1, ht->keyFP) != 1) {
			free(valKey);
			free(filler);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}
		free(filler);

		/*write the current block's next pointer*/
		if ((ht->hashTableOfPointersImageInMemory != NULL) && (firstFlag)){
			ht->hashTableOfPointersImageInMemory[bucket] = newPairPtr;
		}
		else {
			fseek(ht->keyFP, nextPtr, 0);
			if (fwrite(&newPairPtr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP) != 1){
				free(valKey);
				perror(ht->sKeyfileName);
				return DEHT_STATUS_FAIL;
			}
		}
	}
	/*write the new data to dataFP*/
	if (fwrite((signed char *)data, sizeof(unsigned char), dataLength, ht->dataFP) != dataLength){
		free(valKey);
		perror(ht->sDatafileName);
		return DEHT_STATUS_FAIL;
	}
	if (fwrite(&c, sizeof(unsigned char), 1, ht->dataFP) != 1) {
		free(valKey);
		perror(ht->sDatafileName);
		return DEHT_STATUS_FAIL;
	}

	/*update DEHT tables if necessary*/
	if (newBlockFlag){
		if (ht->hashPointersForLastBlockImageInMemory != NULL)
			ht->hashPointersForLastBlockImageInMemory[bucket] = newPairPtr;
	}
	if (ht->anLastBlockSize != NULL){
		ht->anLastBlockSize[bucket]++;
		if (ht->anLastBlockSize[bucket] == ht->header.nPairsPerBlock+1)
			ht->anLastBlockSize[bucket] = 1;
	}

	free(valKey);
	return DEHT_STATUS_SUCCESS;

}

int query_DEHT ( DEHT *ht, const unsigned char *key, int keyLength,
		unsigned char *data, int dataMaxAllowedLength){
	hashKeyIntoTableFunctionPtr hash1 = NULL;
	hashKeyforEfficientComparisonFunctionPtr hash2 = NULL;
	int bucket = 0, blockSize = 0;
	DEHT_DISK_PTR blockPtr = 0;
	void *block = NULL;
	DEHT_DISK_PTR next = 0, dPtr = 0;
	unsigned char *valKey = NULL;

	/*arguments check*/
	if ((ht == NULL) || (key == NULL) || (keyLength < 1) || (data == NULL) || (dataMaxAllowedLength < 1))
		return DEHT_STATUS_FAIL;

	/*flush just in case*/
	fseek(ht->keyFP, 0, 1);
	fseek(ht->dataFP, 0, 1);

	blockSize = ht->header.nPairsPerBlock*(ht->header.nBytesPerValidationKey+sizeof(DEHT_DISK_PTR)) + sizeof(DEHT_DISK_PTR);
	block = calloc(blockSize, 1);
	if (block == NULL)
		return DEHT_STATUS_FAIL;

	hash1 = ht->hashFunc;
	hash2 = ht->comparisonHashFunc;

	bucket = hash1(key, keyLength, ht->header.numEntriesInHashTable);

	valKey = (unsigned char *)calloc(ht->header.nBytesPerValidationKey, sizeof(unsigned char));
	if (valKey == NULL){
		free(block);
		return DEHT_STATUS_FAIL;
	}
	hash2(key, keyLength, valKey);

	/*set blockPtr to the offset of the first block in the bucket's list*/
	if (ht->hashTableOfPointersImageInMemory != NULL)
		blockPtr = ht->hashTableOfPointersImageInMemory[bucket];
	else {
		fseek(ht->keyFP, bucketPos(ht, bucket), 0);
		if (fread(&blockPtr, sizeof(DEHT_DISK_PTR), 1, ht->keyFP) != 1){
			free(block);
			free(valKey);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}
	}

	while (blockPtr != 0){
		if (readBlock(ht, blockPtr, block) == 0){
			free(block);
			free(valKey);
			return DEHT_STATUS_FAIL;
		}
		next = getNextBlock(ht, block);
		dPtr = checkBlock(ht, block, valKey);
		if (dPtr != -1){
			if (getData(ht, getDataPtr(ht,block,dPtr), data, dataMaxAllowedLength) == 0) {
				free(block);
				free(valKey);
				return DEHT_STATUS_FAIL;
			}
			free(block);
			free(valKey);
			return dataMaxAllowedLength;
		}
		blockPtr = next;
	}

	/*we exited the while loop iff the key is not in the bucket*/
	free(block);
	free(valKey);
	return DEHT_STATUS_NOT_NEEDED;
}

/*reads
 *
 */
int read_DEHT_pointers_table(DEHT *ht){
	DEHT_DISK_PTR hashTablePos= 0;
	DEHT_DISK_PTR *table = NULL;
	int numBuckets = 0;

	if (ht == NULL) return DEHT_STATUS_FAIL;

	/*flush just in case*/
	fseek(ht->keyFP, 0, 1);
	fseek(ht->dataFP, 0, 1);

	hashTablePos = (DEHT_DISK_PTR)(sizeof(struct DEHTpreferences) + ht->header.numUnrelatedBytesSaved);
	numBuckets = ht->header.numEntriesInHashTable;

	if (ht->hashTableOfPointersImageInMemory != NULL)
		return DEHT_STATUS_NOT_NEEDED;

	table =(DEHT_DISK_PTR *)calloc(numBuckets, sizeof(DEHT_DISK_PTR));
	if (table == NULL) return DEHT_STATUS_FAIL;
	ht->hashTableOfPointersImageInMemory = table;

	fseek(ht->keyFP, hashTablePos, 0);
	if (fread(ht->hashTableOfPointersImageInMemory, sizeof(DEHT_DISK_PTR), numBuckets, ht->keyFP) != numBuckets){
		perror(ht->sKeyfileName);
		return DEHT_STATUS_FAIL;
	}
	else
		return DEHT_STATUS_SUCCESS;
}

/*write
 * we check if the table is cached if it is we write it to the right place (notice we adjust it by numUnrelatedBytesSaved
 * which is the extra data that can be saved to the table) then free the table from memory
 */
int write_DEHT_pointers_table(DEHT *ht){
	DEHT_DISK_PTR hashTablePos= 0;
	int numBuckets = 0;

	if (ht == NULL) return DEHT_STATUS_FAIL;

	/*flush just in case*/
	fseek(ht->keyFP, 0, 1);
	fseek(ht->dataFP, 0, 1);

	hashTablePos = (DEHT_DISK_PTR)(sizeof(struct DEHTpreferences) + ht->header.numUnrelatedBytesSaved);
	numBuckets = ht->header.numEntriesInHashTable;

	if (ht->hashTableOfPointersImageInMemory == NULL)
		return DEHT_STATUS_NOT_NEEDED;

	if (ftell(ht->keyFP) != hashTablePos)
		fseek(ht->keyFP, hashTablePos, 0);

	if (fwrite(ht->hashTableOfPointersImageInMemory, sizeof(DEHT_DISK_PTR), numBuckets, ht->keyFP) != numBuckets){
		perror(ht->sKeyfileName);
		return DEHT_STATUS_FAIL;
	}
	else {
		free(ht->hashTableOfPointersImageInMemory);
		ht->hashTableOfPointersImageInMemory = NULL;
		return DEHT_STATUS_SUCCESS;
	}
}

/*reads
 * first checks if the array is not cached
 * if not - allocates the mem. according to pre-proc. #define from .h
 * verifies the allocation worked, then reads the table of pointers and
 * runs lastBlock() on each one
 */
int calc_DEHT_last_block_per_bucket(DEHT *ht){
	int i = 0, *sizeArr = NULL, blockSize = 0;
	DEHT_DISK_PTR *ptrs = NULL;
	DEHT_DISK_PTR hashTablePos= 0;
	void *block = NULL;

	if (ht == NULL) return DEHT_STATUS_FAIL;

	/*flush just in case*/
	fseek(ht->keyFP, 0, 1);
	fseek(ht->dataFP, 0, 1);

	hashTablePos = (DEHT_DISK_PTR)(sizeof(struct DEHTpreferences) + ht->header.numUnrelatedBytesSaved);

	if (ht->hashPointersForLastBlockImageInMemory != NULL)
		return DEHT_STATUS_NOT_NEEDED;
	else {
		ptrs = (DEHT_DISK_PTR *)calloc(ht->header.numEntriesInHashTable, sizeof(DEHT_DISK_PTR));
		if (ptrs == NULL) return DEHT_STATUS_FAIL;
		sizeArr = (int *)calloc(ht->header.numEntriesInHashTable, sizeof(int));
		if (sizeArr == NULL) {
			free(ptrs);
			return DEHT_STATUS_FAIL;
		}
		blockSize = ht->header.nPairsPerBlock*(ht->header.nBytesPerValidationKey+sizeof(DEHT_DISK_PTR)) + sizeof(DEHT_DISK_PTR);
		block = calloc(blockSize, 1);
		if (block == NULL){
			free(sizeArr);
			free(ptrs);
			return DEHT_STATUS_FAIL;
		}

		fseek(ht->keyFP, hashTablePos, 0);
		if (fread(ptrs, sizeof(DEHT_DISK_PTR), ht->header.numEntriesInHashTable, ht->keyFP) != ht->header.numEntriesInHashTable)
		{
			free(sizeArr);
			free(ptrs);
			free(block);
			perror(ht->sKeyfileName);
			return DEHT_STATUS_FAIL;
		}

		for (i = 0; i < ht->header.numEntriesInHashTable; i++){
			ptrs[i] = lastBlock(ht, ptrs[i]);
			if (ptrs[i] == 0) {
				sizeArr[i] = 0;
				continue;
			}
			if (readBlock(ht, ptrs[i], block) == 0){
				free(ptrs);
				free(sizeArr);
				free(block);
				return DEHT_STATUS_FAIL;
			}
			sizeArr[i] = getBlockSize(ht, block);

		}
		free(block);
		ht->hashPointersForLastBlockImageInMemory = ptrs;
		ht->anLastBlockSize = sizeArr;
		return DEHT_STATUS_SUCCESS;
	}
}

int lock_DEHT_files(DEHT *ht){

	if (ht != NULL) {
		if (write_DEHT_pointers_table(ht) == DEHT_STATUS_FAIL){
			return DEHT_STATUS_FAIL;
		}
		if (ht->anLastBlockSize != NULL)
			free(ht->anLastBlockSize);
		if (ht->hashPointersForLastBlockImageInMemory != NULL)
			free(ht->hashPointersForLastBlockImageInMemory);

		fclose(ht->keyFP);
		fclose(ht->dataFP);

		free(ht);

		return DEHT_STATUS_SUCCESS;
	}
	else
		return DEHT_STATUS_NOT_NEEDED;
}

/*this function receives a block read generically from file and checks if the key is in one
 * of the pairs in the block.
 * returns: 0 - # pairs in block - 1 (if the key is in)
 * 			-1 if the key is not in the block
 */
int checkBlock(DEHT *ht, void *block, const unsigned char *key){
	const unsigned char *keyPtr = NULL;
	DEHT_DISK_PTR *dataPtr = NULL;
	int numPairs = 0;
	int keyBytes = 0;
	int i = 0, j = 0;
	char sk = 0;

	numPairs = ht->header.nPairsPerBlock;
	keyBytes = ht->header.nBytesPerValidationKey;
	keyPtr = (unsigned char *)block;
	dataPtr = (DEHT_DISK_PTR *)(keyPtr + keyBytes);

	for (i = 0; i < numPairs; i++){
		sk = 0;
		for (j = 0; j < ht->header.nBytesPerValidationKey; j++){
			sk = sk | keyPtr[j];
		}
		if (sk == 0) {
			break;
		}
		if (keyCmp(keyPtr, key, keyBytes) == 0){
			return i;
		}
		keyPtr = (unsigned char *)(dataPtr + 1);
		dataPtr = (DEHT_DISK_PTR *)(keyPtr + keyBytes);
	}
	return -1;
}

/*this function returns the DEHT_DISK_PTR of the pair ( 0 <= pair <= #pairs per block)
 * in the block data
 * assumes block != NULL and pair is a valid index in the above range
 * returns the DEHT_DISK_PTR
 * (this function cannot fail)
 */
DEHT_DISK_PTR getDataPtr(DEHT *ht,void *block, int pair){
	int keyBytes = 0;
	unsigned char *key = NULL;
	DEHT_DISK_PTR *data = NULL;

	keyBytes = ht->header.nBytesPerValidationKey;
	key = (unsigned char *)block;
	key = key + pair*(keyBytes + sizeof(DEHT_DISK_PTR));
	data = (DEHT_DISK_PTR *)(key + keyBytes);

	return *data;
}

/*cannot fail
 *
 */
DEHT_DISK_PTR getNextBlock(DEHT *ht,void *block){
	int numPairs = ht->header.nPairsPerBlock;
	int keyBytes = ht->header.nBytesPerValidationKey;
	int ptrSize = sizeof(DEHT_DISK_PTR);

	return *((DEHT_DISK_PTR *)( ((char *)(block)) + numPairs*(keyBytes + ptrSize)));
}

/*the function reads to char *data the data saved at dataPtr in the .data file
 *returns 0 on fail
 *returns 1 on success
*/
int getData(DEHT *ht, DEHT_DISK_PTR dataPtr, const unsigned char *data, int dataMaxAllowedLength){
	unsigned char buffer[80] = {0};

	fseek(ht->dataFP, dataPtr, 0);
	/*do the reading but since we don't know how big the string is and where ini the file it is
	 * we can only see that we read something instead of nothing
	 */
	if (fread(buffer, sizeof(unsigned char), dataMaxAllowedLength, ht->dataFP) == 0){
		perror(ht->sDatafileName);
		return 0;
	}
	/*mark the end of the string in the max. allowed size then copy to
	 * the dataBuffer - strcpy will exit on the first '\0' it will see
	 * so we get the first string in the bufferreadBlock
	 */
	buffer[dataMaxAllowedLength-1] = '\0';
	strcpy((char *)data, (char *)buffer);

	return 1;
}


/*cannot fail
 * returns a value i s.t:     0 <= i <= (pairs per block) 
	according to how many occupied pairs are in the block
	i == (pairs per block) meaning it is full
*/
int getBlockSize(DEHT *ht,void *block){
	int i = 0, j = 0, sk = 0;
	DEHT_DISK_PTR *dataPtr = NULL;
	unsigned char *keyPtr = NULL;

	dataPtr = (DEHT_DISK_PTR *)block;
	dataPtr--;

	for (i = 0; i < ht->header.nPairsPerBlock; i++){
		keyPtr = (unsigned char *)(dataPtr +1);
		dataPtr = (DEHT_DISK_PTR *)(keyPtr + ht->header.nBytesPerValidationKey);
		sk = 0;
		for (j = 0; j < ht->header.nBytesPerValidationKey; j++){
			sk += (int)(keyPtr[j]);
		}
		if (sk == 0) return i;
	}

	return i;
}

/*calculates the offset of the bucket in the table*/
DEHT_DISK_PTR bucketPos(DEHT *ht, int b){
	return sizeof(struct DEHTpreferences) + ht->header.numUnrelatedBytesSaved + b*sizeof(DEHT_DISK_PTR);
}

/*reads
 * read the block next pointer
 */
DEHT_DISK_PTR nextBlock(DEHT *ht, DEHT_DISK_PTR block){
	DEHT_DISK_PTR next = 0;

	fseek(ht->keyFP, block + ht->header.nPairsPerBlock*(ht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR)), 0);
	if (fread(&next, sizeof(DEHT_DISK_PTR), 1, ht->keyFP) != 1){
		perror(ht->sKeyfileName);
		return -1;
	}
	else
		return next;
}

/*reads the block starting at blockPtr to the block buffer
 *return
 */
int readBlock(const DEHT *ht,const DEHT_DISK_PTR blockPtr, void *block){
	int blockSize = 0;

	blockSize = ht->header.nPairsPerBlock*(ht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR)) + sizeof(DEHT_DISK_PTR);
	if (ftell(ht->keyFP) != blockPtr)
		fseek(ht->keyFP, blockPtr, 0);

	if (fread(block, blockSize, 1, ht->keyFP) != 1){
		perror(ht->sKeyfileName);
		return 0;
	}
	else
		return 1;
}

/*reads
 *if the block == 0 (happens if the block is an empty bucket) return 0
 */
DEHT_DISK_PTR lastBlock(DEHT *ht, DEHT_DISK_PTR block){
	DEHT_DISK_PTR p = 0, q = 0;
	int numPairs = 0, keyBytes = 0;

	numPairs = ht->header.nPairsPerBlock;
	keyBytes = ht->header.nBytesPerValidationKey;

	if (block == 0) return 0;  /*block is an empty bucket*/

	q = block;
	fseek(ht->keyFP, block + numPairs*(sizeof(DEHT_DISK_PTR) + keyBytes), 0);
	if (fread(&p, sizeof(DEHT_DISK_PTR), 1, ht->keyFP) != 1){
		perror(ht->sKeyfileName);
		return -1;
	}

	while (p != 0){
		q = p;
		p = nextBlock(ht, p);
		if (p == -1) return -1;
	}

	return q;
}

int hash1(const unsigned char *keyBuf, int keyLength, int nTableSize){
	return pseudo_random_function(keyBuf, keyLength, 0x3efab00b1e) % nTableSize;
}

int hash2(const unsigned char * keyBuf, int keyLength, unsigned char *valKeyBuf){
	const unsigned char *a = NULL;
	unsigned char *b = NULL;
	int i = 0;


	a = keyBuf;
	b = valKeyBuf;

	for (i = 0; i < 8; i++){
		*b = *a;
		a++;
		b++;
	}
	return 1;
}

/*a debugging function for writing the data saved to the DEHT -- this function exists only for text_export of part three
	and is a debuggin tool!!!!!!!!*/
int textExportDEHT(DEHT *deht, FILE *file){
	int i = 0, j = 0, k = 0, sk = 0, counter = 0, blockSize = 0;
	DEHT_DISK_PTR b = 0, *data = NULL;
	void *block = NULL;
	unsigned char *key = NULL, dataBuffer[80] = {0};

	blockSize = deht->header.nPairsPerBlock*(deht->header.nBytesPerValidationKey + sizeof(DEHT_DISK_PTR)) + sizeof(DEHT_DISK_PTR);
	block = calloc(1, blockSize);
	if (block == NULL) {
		fprintf(stderr, "failed to allocate mem. for block object\n");
		return 1;
	}


	for (i = 0; i < deht->header.numEntriesInHashTable; i++){
		b = deht->hashTableOfPointersImageInMemory[i];
		while (b != 0){
			if (readBlock(deht,b, block) == 0) {
				fprintf(stderr, "reading failuer\n");
				return 0;
			}
			data = block;
			data--;
			for (j = 0; j < deht->header.nPairsPerBlock; j++){
				key = (unsigned char *)(data + 1);
				data = (DEHT_DISK_PTR *)(key + deht->header.nBytesPerValidationKey);
				sk = 0;
				for (k = 0; k < deht->header.nBytesPerValidationKey; k++){
					sk += (int)(key[k]);
				}
				if (sk == 0) break;
				getData(deht, *data, dataBuffer, 80);
				fprintf(file, "%s\n", (signed char *)dataBuffer);
				counter++;
			}
			if (j < deht->header.nPairsPerBlock-1) break;
			b = getNextBlock(deht,block);
		}
	}
	return 1;
}
